﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interactivity;

namespace Wheels.Windows
{
    public class HotKeyInputBehavoir
        : Behavior<TextBox>
    {
#pragma warning disable 1591

        public struct HotKey
        {
            public ModifierKeys Modifiers;
            public Key Key;
        }

        public static readonly DependencyProperty HotKeyProperty = DependencyProperty.Register("HotKey", typeof(HotKey), typeof(TextBox));
        public static HotKey GetHotKey(TextBox textbox)
        {
            return (HotKey)textbox.GetValue(HotKeyProperty);
        }

        private TextBox InputBox { get; set; }

        protected override void OnAttached()
        {
            this.InputBox = this.AssociatedObject;

            InputBox.ContextMenu = null;

            DataObject.AddCopyingHandler(InputBox, CancelCopying);
            DataObject.AddPastingHandler(InputBox, CancelPasting);

            InputBox.SelectionChanged += InputBox_SelectionChanged;
            InputBox.PreviewDragEnter += InputBox_CancelDrag;
            //InputBox.PreviewDragLeave += InputBox_CancelDrag;
            InputBox.PreviewDragOver += InputBox_CancelDrag;

            InputBox.PreviewKeyDown += InputBox_PreviewKeyDown;
            InputBox.PreviewKeyUp += InputBox_PreviewKeyUp;
        }

        protected override void OnDetaching()
        {
            InputBox.ClearValue(FrameworkElement.ContextMenuProperty);

            DataObject.RemoveCopyingHandler(InputBox, CancelCopying);
            DataObject.RemovePastingHandler(InputBox, CancelPasting);

            InputBox.SelectionChanged -= InputBox_SelectionChanged;
            InputBox.PreviewDragEnter -= InputBox_CancelDrag;
            //InputBox.PreviewDragLeave -= InputBox_CancelDrag;
            InputBox.PreviewDragOver -= InputBox_CancelDrag;

            InputBox.PreviewKeyDown -= InputBox_PreviewKeyDown;
            InputBox.PreviewKeyUp -= InputBox_PreviewKeyUp;

            this.InputBox = null;
        }

        #region guardian events

        private void CancelCopying(object sender, DataObjectCopyingEventArgs e)
        {
            e.CancelCommand();
        }
        private void CancelPasting(object sender, DataObjectPastingEventArgs e)
        {
            e.CancelCommand();
        }

        void InputBox_CancelDrag(object sender, DragEventArgs e)
        {
            e.Effects = DragDropEffects.None;
            e.Handled = true;
        }

        void InputBox_SelectionChanged(object sender, RoutedEventArgs e)
        {
            var text = InputBox.Text;
            if (text == null) return;

            if (InputBox.SelectionStart < text.Length)
                InputBox.SelectionStart = text.Length;
            if (InputBox.SelectionLength != 0)
                InputBox.SelectionLength = 0;
        }

        #endregion

        private static bool IsModifierKey(Key key)
        {
            switch (key)
            {
                case Key.LeftCtrl:
                case Key.LeftAlt:
                case Key.LeftShift:
                case Key.RightCtrl:
                case Key.RightAlt:
                case Key.RightShift:
                case Key.LWin:
                case Key.RWin:
                    return true;
                default:
                    return false;
            }
        }

        private static ModifierKeys GetKeyboardModifiers()
        {
            if (Keyboard.IsKeyDown(Key.LWin) || Keyboard.IsKeyDown(Key.RWin))
                return Keyboard.Modifiers | ModifierKeys.Windows;
            return Keyboard.Modifiers;
        }

        private static string MakeModifierText(ModifierKeys modifiers)
        {
            var sb = new StringBuilder();

            if ((modifiers & ModifierKeys.Control) != 0)
                sb.Append("Ctrl + ");
            if ((modifiers & ModifierKeys.Alt) != 0)
                sb.Append("Alt + ");
            if ((modifiers & ModifierKeys.Shift) != 0)
                sb.Append("Shift + ");
            if ((modifiers & ModifierKeys.Windows) != 0)
                sb.Append("Win + ");

            return sb.ToString();
        }

        private static string MakeHotKeyText(HotKey hotkey)
        {
            return MakeHotKeyText(hotkey.Key, hotkey.Modifiers);
        }
        private static string MakeHotKeyText(Key key, ModifierKeys modifiers)
        {
            var sb = new StringBuilder();

            if (modifiers == 0)
                sb.Append("Ctrl + Alt + ");
            else
                sb.Append(MakeModifierText(modifiers));

            if (key != 0)
                sb.Append(key.ToString());

            return sb.ToString();
        }

        private Key mKey;

        private Key mLastKeyDown;
        private void InputBox_PreviewKeyDown(object sender, KeyEventArgs e)
        {
            e.Handled = true;

            Key key = (e.Key == Key.System ? e.SystemKey : e.Key);

            if (key == mLastKeyDown)
                return;
            else
                mLastKeyDown = key;

            if (IsModifierKey(key))
            {
                if (mKey != 0 && Keyboard.IsKeyUp(mKey))
                    mKey = Key.None;
            }
            else
            {
                mKey = key;
            }

            var hk = new HotKey
            {
                Modifiers = GetKeyboardModifiers(),
                Key = mKey,
            };
            InputBox.SetValue(HotKeyProperty, hk);

            var hkText = MakeHotKeyText(hk);
            this.InputBox.Text = hkText;
            this.InputBox.SelectionStart = hkText.Length;
        }

        void InputBox_PreviewKeyUp(object sender, KeyEventArgs e)
        {
            e.Handled = true;

            Key key = (e.Key == Key.System ? e.SystemKey : e.Key);

            if (key == mLastKeyDown)
                mLastKeyDown = 0;

            if (IsModifierKey(key))
            {
                if (mKey != 0 && Keyboard.IsKeyUp(mKey))
                    return;
            }
            else
            {
                return;
            }

            var hk = new HotKey
            {
                Modifiers = GetKeyboardModifiers(),
                Key = mKey,
            };
            InputBox.SetValue(HotKeyProperty, hk);

            var hkText = MakeHotKeyText(hk);
            this.InputBox.Text = hkText;
            this.InputBox.SelectionStart = hkText.Length;
        }
    }
}
